package cn.yangliu.jpa.tools;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import cn.yangliu.jpa.annotations.jpql.JpaJpql;
import cn.yangliu.jpa.annotations.jpql.JpaJpqlQuery;
import cn.yangliu.jpa.annotations.qbc.JpaQbcQuery;
import cn.yangliu.jpa.annotations.sql.JpaSql;
import cn.yangliu.jpa.annotations.sql.JpaSqlQuery;
import cn.yangliu.jpa.enums.DbTypeEnum;
import cn.yangliu.jpa.enums.LinkTypeEnum;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.CollectionUtils;

/**
 * The interface Param transformer.
 *
 * @author 问道于盲
 * @date 2019-06-14
 */
public class JpaUtils {

	/**
	 * current class's static member
	 * The constant INSTANCE.
	 */
	public static final JpaUtils INSTANCE = new JpaUtils();

	/**
	 * current class's static member
	 * The constant WHERE.
	 */
	public static final String WHERE = " where ";

	/**
	 * current class instance's member.
	 * The Defaultdbtype.
	 */
	private DbTypeEnum defaultdbtype = DbTypeEnum.MYSQL;

	private static DbTypeEnum GLOBAL_DBTYPE;

	/**
	 * current class's static member
	 * The constant THREAD_LOCAL.
	 */
	public static final ThreadLocal<DbTypeEnum> THREAD_LOCAL = new ThreadLocal<>();

	/**
	 * 获取查询对象的所有属性 获取父类属性是为了保证继承
	 *
	 * @param object 查询条件封装 或者class对象
	 * @return 属性集合 declared fields
	 */
	public static List<Field> getDeclaredFields(Object object) {
		Class<?> clazz;
		if (object instanceof Class) {
			clazz = (Class<?>) object;
		} else {
			clazz = object.getClass();
		}

		List<Field> fieldList = new ArrayList<>();
		while (!clazz.equals(Object.class)) {
			Field[] fields = clazz.getDeclaredFields();
			if (fields.length > 0) {
				fieldList.addAll(Arrays.asList(fields));
			}
			clazz = clazz.getSuperclass();
		}
		return fieldList;
	}




	public static List<Field> sort(List<Field> fieldList, Class<?> annotationClass) {
		List<FieldInfo> fieldInfos = fieldList.stream().map(field -> {
			int order = Integer.MAX_VALUE;

			if (Objects.equals(annotationClass, JpaJpqlQuery.class)) {
				JpaJpqlQuery jpaJpqlQuery = field.getDeclaredAnnotation(JpaJpqlQuery.class);
				if (Objects.nonNull(jpaJpqlQuery)) {
					order = jpaJpqlQuery.order();
				}
			}
			if (Objects.equals(annotationClass, JpaQbcQuery.class)) {
				JpaQbcQuery jpaQbcQuery = field.getDeclaredAnnotation(JpaQbcQuery.class);
				if (Objects.nonNull(jpaQbcQuery)) {
					order = jpaQbcQuery.order();
				}
			}

			if (Objects.equals(annotationClass, JpaSqlQuery.class)) {
				JpaSqlQuery jpaSqlQuery = field.getDeclaredAnnotation(JpaSqlQuery.class);
				if (Objects.nonNull(jpaSqlQuery)) {
					order = jpaSqlQuery.order();
				}
			}

			return new FieldInfo(field, order);
		}).collect(Collectors.toList());
		fieldInfos.sort(Comparator.comparingInt(FieldInfo::getOrder));
		return fieldInfos.stream().map(FieldInfo::getField).collect(Collectors.toList());
	}

	static class FieldInfo {
		private Field field;
		private int order = Integer.MAX_VALUE;

		public FieldInfo(Field field, int order) {
			this.field = field;
			this.order = order;
		}

		/**
		 * get field
		 *
		 * @return field
		 */
		public Field getField() {
			return field;
		}

		/**
		 * set field
		 *
		 * @param field field
		 */
		public void setField(Field field) {
			this.field = field;
		}

		/**
		 * get order
		 *
		 * @return order
		 */
		public int getOrder() {
			return order;
		}

		/**
		 * set order
		 *
		 * @param order order
		 */
		public void setOrder(int order) {
			this.order = order;
		}
	}

	/**
	 * Link type string.
	 *
	 * @param linkType the link type
	 * @param flag     the flag
	 * @return the string
	 */
	public static String linkType(LinkTypeEnum linkType, boolean flag) {
		if (!flag) {
			return " ";
		}
		return " " + linkType.name() + " ";
	}

	/**
	 * 获取属性值
	 *
	 * @param queryPojo 查询对象
	 * @param field     属性
	 * @return 值 field value
	 */
	public static Object getFieldValue(Object queryPojo, Field field) {
		boolean accessible = field.isAccessible();
		if (!accessible) {
			field.setAccessible(true);
		}
		try {
			return field.get(queryPojo);

		} catch (Exception e) {
			throw new UnsupportedOperationException(e.getMessage(), e);
		} finally {
			if (!accessible) {
				field.setAccessible(false);
			}
		}
	}

	/**
	 * 驼峰转下划线
	 *
	 * @param para 源字符串
	 * @return 带下划线的字符串 string
	 */
	public static String humpToUnderline(String para) {
		StringBuilder sb = new StringBuilder(para);
		// 定位
		int temp = 0;
		if (!para.contains("_")) {
			for (int i = 0; i < para.length(); i++) {
				if (Character.isUpperCase(para.charAt(i))) {
					sb.insert(i + temp, "_");
					temp += 1;
				}
			}
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * current class method for setter.
	 * Sets field value.
	 *
	 * @param object the object
	 * @param field  the field
	 * @param value  the value
	 */
	public static void setFieldValue(Object object, Field field, Object value) {
		boolean accessible = field.isAccessible();
		if (!accessible) {
			field.setAccessible(true);
		}
		try {
			field.set(object, value);
		} catch (Exception e) {
			throw new UnsupportedOperationException(e.getMessage(), e);
		} finally {
			if (!accessible) {
				field.setAccessible(false);
			}
		}
	}

	/**
	 * Build array params.
	 *
	 * @param sb    the sb
	 * @param value the value
	 */
	public static void buildArrayParams(StringBuilder sb, Object value) {
		sb.append(" (");
		if (value.getClass().isArray()) {
			Object[] array = (Object[]) value;
			for (int i = 0; i < array.length; i++) {
				if (array[i] instanceof String) {
					sb.append("'").append(array[i].toString()).append("'");
					if (i + 1 != array.length) {
						sb.append(",");
					}
				}
				if (array[i] instanceof Number) {
					sb.append(array[i]);
					if (i + 1 != array.length) {
						sb.append(",");
					}
				}

			}
		}
		if (value instanceof Collection) {
			Collection<?> collection = (Collection<?>) value;
			int index = 0;
			for (Object o : collection) {
				if (o instanceof String) {
					sb.append("'").append(o.toString()).append("'");
					if ((index + 1) != collection.size()) {
						sb.append(",");
					}
				}
				if (o instanceof Number) {
					sb.append(o);
					if ((index + 1) != collection.size()) {
						sb.append(",");
					}
				}
				index++;
			}
		}
		sb.append(") ");
	}

	/**
	 * Is not empty array boolean.
	 *
	 * @param value the value
	 * @return the boolean
	 */
	public static boolean isNotEmptyArray(Object value) {
		if (Objects.isNull(value)) {
			return false;
		}
		return (value.getClass().isArray() && ((Object[]) value).length > 0)
				|| ((value instanceof Collection) && ((Collection<?>) value).size() > 0);
	}

	/**
	 * current class's static member
	 * The constant LOGGER.
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(JpaUtils.class);

	/**
	 * Close.
	 *
	 * @param closeables the closeables
	 */
	public static void close(AutoCloseable... closeables) {
		if (Objects.isNull(closeables)) {
			return;
		}

		for (AutoCloseable closeable : closeables) {
			try {
				closeable.close();
			} catch (Exception e) {
				LOGGER.debug(e.getMessage(), e);
			}
		}
	}

	/**
	 * Target field name string.
	 *
	 * @param jpaJpqlQuery the jpa jpql query
	 * @param field        the field
	 * @return the string
	 */
	public static String targetFieldName(JpaJpqlQuery jpaJpqlQuery, Field field) {
		String fieldName = jpaJpqlQuery.parameter();

		if (StringUtils.isBlank(fieldName)) {
			fieldName = field.getName();
		}
		return fieldName;
	}


	/**
	 * set defaultdbtype
	 *
	 * @param defaultdbtype defaultdbtype
	 */
	public static void setDefaultdbtype(DbTypeEnum defaultdbtype) {
		INSTANCE.defaultdbtype = defaultdbtype;
	}

	/**
	 * get defaultdbtype
	 *
	 * @return defaultdbtype defaultdbtype
	 */
	public static DbTypeEnum getDefaultdbtype() {
		return INSTANCE.defaultdbtype;
	}


	/**
	 * current class method for getter.
	 * Gets entity class.
	 *
	 * @param <T>       the type parameter
	 * @param queryPojo the query pojo
	 * @return the entity class
	 */
	@SuppressWarnings("unchecked")
	public static <T> Class<T> getEntityClass(Object queryPojo) {
		JpaJpql jpaJpql = queryPojo.getClass().getDeclaredAnnotation(JpaJpql.class);
		if (Objects.isNull(jpaJpql)) {
			throw new NullPointerException(queryPojo.getClass().getName() + " is not declared by @JpaJpql");
		}
		return (Class<T>)jpaJpql.resultClass();
	}


	/**
	 * current class method for getter.
	 * Gets source jpql.
	 *
	 * @param queryPojo the query pojo
	 * @return the source jpql
	 */
	public static String getSourceJpql(Object queryPojo) {
		JpaJpql jpaJpql = queryPojo.getClass().getDeclaredAnnotation(JpaJpql.class);
		if (Objects.isNull(jpaJpql)) {
			throw new NullPointerException(queryPojo.getClass().getName() + " is not declared by @JpaJpql");
		}
		String sourceJpql = jpaJpql.sourceJpql();
		if (!StringUtils.isBlank(sourceJpql)) {
			return sourceJpql;
		}

		Class<Object> entityClass = getEntityClass(queryPojo);
		sourceJpql = "from " + entityClass.getSimpleName();
		return sourceJpql;
	}

	/**
	 * current class method for getter.
	 * Gets source sql.
	 *
	 * @param queryPojo the query pojo
	 * @return the source sql
	 */
	public static String getSourceSql(Object queryPojo) {
		JpaSql jpaSql = queryPojo.getClass().getDeclaredAnnotation(JpaSql.class);
		if (Objects.isNull(jpaSql)) {
			throw new NullPointerException(queryPojo.getClass().getName() + " is not declared by @JpaSql");
		}
		String sourceSql = jpaSql.sourceSql();
		if (StringUtils.isBlank(sourceSql)) {
			throw new NullPointerException(queryPojo.getClass().getName() + " is declared by @JpaSql, but annotation not set value() method for source sql");
		}
		return sourceSql;
	}

	/**
	 * current class method for getter.
	 * Gets result class.
	 *
	 * @param <T>       the type parameter
	 * @param queryPojo the query pojo
	 * @return the result class
	 */
	@SuppressWarnings("unchecked")
	public static <T> Class<T> getResultClass(Object queryPojo) {
		JpaSql jpaSql = queryPojo.getClass().getDeclaredAnnotation(JpaSql.class);
		if (Objects.isNull(jpaSql)) {
			throw new NullPointerException(queryPojo.getClass().getName() + " is not declared by @JpaSql");
		}

		return (Class<T>)jpaSql.resultClass();
	}

	/**
	 * 构建排序
	 *
	 * @param sorted     是否排序
	 * @param orders     排序字段
	 * @param directions 排序字段倒序还是逆序
	 * @return 排序语句 string
	 */
	public static String buildOrderBy(boolean sorted, List<String> orders, List<String> directions) {
		if (!sorted) {
			return "";
		}
		return buildOrderBy(orders, directions);
	}

	/**
	 * Build order by string.
	 *
	 * @param orders     the orders
	 * @param directions the directions
	 * @return the string
	 */
	public static String buildOrderBy(List<String> orders, List<String> directions) {
		if (CollectionUtils.isEmpty(orders)|| CollectionUtils.isEmpty(directions) ){
			return "";
		}
		StringBuilder sb = new StringBuilder(" order by ");

		for (int i = 0; i < orders.size(); i++) {
			String property = orders.get(i);
			String direction = directions.get(i);
			sb.append(property).append(" ").append(direction);
			if ((i + 1) != orders.size()) {
				sb.append(",");
			}
		}
		return sb.toString();
	}

	/**
	 * Build orders.
	 *
	 * @param sort       the sort
	 * @param orders     the orders
	 * @param directions the directions
	 */
	public static void buildOrders(Sort sort, List<String> orders, List<String> directions){
		if (Objects.isNull(sort) || !sort.isSorted()) {
			return;
		}
		Iterator<Sort.Order> iterator = sort.iterator();
		while (iterator.hasNext()) {
			Sort.Order order = iterator.next();
			String property = order.getProperty();
			orders.add(property);
			String direction = order.getDirection().name();
			directions.add(direction);
		}

	}


	/**
	 * Is sorted boolean.
	 *
	 * @param pageable   the pageable
	 * @param orders     the orders
	 * @param directions the directions
	 * @return the boolean
	 */
	public static boolean isSorted(Pageable pageable, List<String> orders, List<String> directions){
		boolean sorted = false;
		if (Objects.nonNull(pageable)) {
			sorted = pageable.getSort().isSorted();
		}
		if (sorted) {
			buildOrders(pageable.getSort(), orders, directions);
		}
		return sorted;
	}


	/**
	 * current class method for getter.
	 * Gets db type.
	 *
	 * @return the db type
	 */
	public static DbTypeEnum getDbType() {
		DbTypeEnum dbTypeEnum = JpaUtils.THREAD_LOCAL.get();
		if (Objects.nonNull(dbTypeEnum)) {
			return dbTypeEnum;
		}
		if (Objects.nonNull(GLOBAL_DBTYPE)) {
			return GLOBAL_DBTYPE;
		}

		return INSTANCE.defaultdbtype;
	}

	/**
	 * 设置全局数据库类型
	 *
	 * @param globalDbType globalDbType
	 */
	public static void setGlobalDbType(DbTypeEnum globalDbType) {
		GLOBAL_DBTYPE = globalDbType;
	}
}
